<template>
    <MyArticle class="readingNotes-webgl-04" title="WebGL学习笔记之四：颜色与纹理" date="2021年02月13日">
        <div class="_article-content">
            <p>将主要讲解图元光栅化过程，纹理映射到图形或者三维对象表明的方法。</p>
            <h3 class="title">顶点着色器传入非坐标数据</h3>
            <p>通过设置多个缓冲区（点的坐标数据、点的大小数据），以实现动态设置点的大小：</p>
            <canvas ref="canvasRef0" v-show="!img0" class="webgl-canvas spec-canvas"></canvas>
            <img :src="img0" v-show="img0" class="webgl-canvas spec-canvas"/>
            <pre><code>// 顶点着色器
const VSHADER_SOURCE = `
    attribute vec4 a_Position;
    attribute float a_PointSize;
    void main() {
        gl_Position = a_Position;
        gl_PointSize = a_PointSize;
    }
`</code></pre>
            <pre><code>const initVertexBuffers = (gl) => {
    const vertices = new Float32Array(vertexDatas) // [-0.5, -0.5, 0.5, -0.5, 0, 0.5]
    const sizes = new Float32Array(sizeDatas) // [8.0, 16.0, 32.0]
    const n = vertexCount
    // 创建缓冲区
    const verticesBuffer = gl.createBuffer()
    const sizesBuffer = gl.createBuffer()
    if (!verticesBuffer || !sizesBuffer) {
        return -1
    }
    // 将缓冲区对象绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer)
    // 向缓冲区对象中写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
    const aPosition = gl.getAttribLocation(gl.program, 'a_Position')
    // 将缓冲区对象分配给attribute变量
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0)
    // 连接a_Position变量与分配给它的缓冲区对象
    gl.enableVertexAttribArray(aPosition)

    // 接下来处理点的尺寸数据
    gl.bindBuffer(gl.ARRAY_BUFFER, sizesBuffer)
    gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW)
    const aPointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
    gl.vertexAttribPointer(aPointSize, 1, gl.FLOAT, false, 0, 0)
    gl.enableVertexAttribArray(aPointSize)
    return n
}</code></pre>
            <p>利用vertexAttribPointer()函数，<strong>点的位置与大小数据交错在一个缓冲区中</strong>，也能实现上面效果：</p>
            <pre><code>const initVertexBuffers = (gl) => {
    const vertices = new Float32Array(vertexDatas) // [-0.5, -0.5, 8.0, 0.5, -0.5, 16.0, 0, 0.5, 32.0]
    const n = vertexCount
    // 创建缓冲区
    const verticesBuffer = gl.createBuffer()
    if (!verticesBuffer) {
        return -1
    }
    // 将缓冲区对象绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer)
    // 向缓冲区对象中写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
    const eleSize = vertices.BYTES_PER_ELEMENT

    // 处理position
    const aPosition = gl.getAttribLocation(gl.program, 'a_Position')
    // 将缓冲区对象分配给attribute变量
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 3 * eleSize, 0) // 每3个元素一组，从0个元素开始
    // 连接a_Position变量与分配给它的缓冲区对象
    gl.enableVertexAttribArray(aPosition)

    // 处理point size
    const aPointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
    gl.vertexAttribPointer(aPointSize, 1, gl.FLOAT, false, 3 * eleSize, 2 * eleSize) // 每3个元素一组，从第2个元素开始
    gl.enableVertexAttribArray(aPointSize)
    return n
}</code></pre>
            <canvas ref="canvasRef1" v-show="!img1" class="webgl-canvas spec-canvas"></canvas>
            <img :src="img1" v-show="img1" class="webgl-canvas spec-canvas"/>
            <p>将顶点的坐标和尺寸数据打包到同一个缓冲区对象中，并通过vertexAttribPointer方法设置<strong>步进和偏移量</strong>，访问缓冲区对象中不同种类的数据。</p>
            <h3 class="title">varying变量</h3>
            <p>varying变量的作用是从顶点着色器向片元着色器传输数据。</p>
            <canvas ref="canvasRef2" v-show="!img2" class="webgl-canvas spec-canvas"></canvas>
            <img :src="img2" v-show="img2" class="webgl-canvas spec-canvas"/>
            <pre><code>// 顶点着色器
const VSHADER_SOURCE = `
    attribute vec4 a_Position;
    attribute float a_PointSize;
    attribute vec4 a_Color;
    varying vec4 v_Color;
    void main() {
        gl_Position = a_Position;
        gl_PointSize = a_PointSize;
        v_Color = a_Color;
    }
`
// 片元着色器
const FSHADER_SOURCE = `
    precision mediump float;
    varying vec4 v_Color;
    void main() {
        gl_FragColor = v_Color;
    }
`</code></pre>
            <p><strong>在WebGl中，如果顶点着色器与片元着色器有类型和命名都相同的varying变量，那么顶点着色器赋给该变量的值就会自动传入片元着色器。</strong></p>
            <p>彩色三角形：</p>
            <canvas ref="canvasRef3" v-show="!img3" class="webgl-canvas spec-canvas"></canvas>
            <img :src="img3" v-show="img3" class="webgl-canvas spec-canvas"/>
            <p>在顶点着色器与片元着色器之间，进行图形装配过程和光栅化过程。图形装配即将孤立的顶点坐标装配成几何图形，几何图形的类别由gl.drawArrays()函数的第一个参数决定。光栅化即将几何图形转换成片元。</p>
            <p>片元着色器的逐片元执行：</p>
            <pre><code>const FSHADER_SOURCE = `
    precision mediump float;
    uniform float u_Width;
    uniform float u_Height;
    void main() {
        gl_FragColor = vec4(gl_FragCoord.x/u_Width, 0.0, gl_FragCoord.y/u_Height, 1.0);
    }
`</code></pre>
            <p>u_Width和u_Height取canvas元素的宽高：</p>
            <pre><code>const uWidth = gl.getUniformLocation(gl.program, 'u_Width')
const uHeight = gl.getUniformLocation(gl.program, 'u_Height')
gl.uniform1f(uWidth, el.width)
gl.uniform1f(uHeight, el.height)</code></pre>
            <canvas ref="canvasRef4" v-show="!img4" class="webgl-canvas spec-canvas"></canvas>
            <img :src="img4" v-show="img4" class="webgl-canvas spec-canvas"/>
            <h3 class="title">在矩形表面贴图</h3>
            <p><strong>纹理映射</strong>是将一张图像映射（贴）到一个几何图形表面上去。其作用是为之前光栅化之后的片元涂上合适的颜色。组成纹理图像的像素称为纹素。在WebGL中进行纹理映射的步骤如下：</p>
            <p>1、准备好映射到几何图形上的纹理图像；2、为几何图形配置纹理映射方式；3、加载纹理图像，对其进行一些配置；4、在片元着色器中将相应的纹素抽取出来，赋给片元。</p>
            <p>WebGl中的纹理坐标是二维的，左下角为原点（0.0, 0.0），右上角为（1.0, 1.0）。</p>
            <canvas ref="canvasRef5" v-show="!img5" class="webgl-canvas spec-canvas"></canvas>
            <img :src="img5" v-show="img5" class="webgl-canvas spec-canvas"/>
            <p>两个着色器程序的定义：</p>
            <pre><code>// 顶点着色器
const VSHADER_SOURCE = `
    attribute vec4 a_Position;
    attribute vec2 a_TexCoord;
    varying vec2 v_TexCoord;
    void main() {
        gl_Position = a_Position;
        v_TexCoord = a_TexCoord;
    }
`
// 片元着色器
const FSHADER_SOURCE = `
    precision mediump float;
    uniform sampler2D u_Sampler;
    varying vec2 v_TexCoord;
    void main() {
        gl_FragColor = texture2D(u_Sampler, v_TexCoord);
    }
`</code></pre>
            <p>定义顶点坐标与纹理坐标之间的对应关系，将数据写入缓冲区对象：</p>
            <pre><code>const vertexDatas = [-0.5, 0.5, 0.0, 1.0,
    -0.5, -0.5, 0.0, 0.0,
    0.5, 0.5, 1.0, 1.0,
    0.5, -0.5, 1.0, 0.0
]
const initVertexBuffers = (gl) => {
    const vertices = new Float32Array(vertexDatas)
    const n = vertexCount
    // 创建缓冲区
    const verticesBuffer = gl.createBuffer()
    if (!verticesBuffer) {
        return -1
    }
    // 将缓冲区对象绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer)
    // 向缓冲区对象中写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
    const eleSize = vertices.BYTES_PER_ELEMENT
    const aPosition = gl.getAttribLocation(gl.program, 'a_Position')
    // 将缓冲区对象分配给attribute变量
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 4 * eleSize, 0)
    // 连接a_Position变量与分配给它的缓冲区对象
    gl.enableVertexAttribArray(aPosition)
    const aTexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord')
    gl.vertexAttribPointer(aTexCoord, 2, gl.FLOAT, false, 4 * eleSize, 2 * eleSize)
    gl.enableVertexAttribArray(aTexCoord)
    return n
}</code></pre>
            <p>初始化纹理对象，加载图片：</p>
            <pre><code>const initTextures = (gl, n) => {
    const texture = gl.createTexture() // 创建纹理对象
    const uSampler = gl.getUniformLocation(gl.program, 'u_Sampler') // 获取取样器的位置
    const image = new Image()
    image.onload = function () {
        loadTexture(gl, n, texture, uSampler, image)
    }
    image.src = './logo.jpg'
    return true
}</code></pre>
            <p>从纹理图像中获取纹素颜色的过程，相当于从纹理图像中取样。即输入纹理坐标返回颜色值的过程。gl.createTexture()创建纹理对象，gl.deleteTexture(texture)可以删除纹理对象。</p>
            <p>配置纹理对象，绘制图形：</p>
            <pre><code>const loadTexture = (gl, n, texture, uSampler, image) => {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) // 对纹理图像进行Y轴翻转
    gl.activeTexture(gl.TEXTURE0) // 开启0号纹理单元
    gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理对象
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // 配置纹理参数
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image) // 配置纹理图像
    gl.uniform1i(uSampler, 0) // 将0号纹理传递给取样器

    gl.clearColor(0.0, 0.0, 0.0, 1.0)
    gl.clear(gl.COLOR_BUFFER_BIT)
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, n)
}</code></pre>
            <p>WebGl纹理坐标系统中的t轴的方向与图片的坐标系统Y轴方向是相反的，gl.pixelStorei的定义如下：</p>
            <pre><code>gl.pixelStorei(pname, param) // pname取值gl.UNPACK_FLIP_Y_WEBGL（对图像进行Y轴反转）、
// gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL（将图像rgb颜色值的每一个分量乘A
// param：指定非0（true）或0（false），必须为整数
</code></pre>
            <p>WebGl中通过使用纹理单元的机制来同时使用多个纹理，每个纹理单元有个编号，从gl.TEXTURE0~gl.TEXTURE7。在使用之前，需要调用gl.activeTexture()来激活它。</p>
            <pre><code>gl.activeTexture(texUnit) // texUnit取值gl.TEXTURE0、gl.TEXTURE1...gl.TEXTURE7</code></pre>
            <p>纹理对象有两种：<strong>gl.TEXTURE_2D（二维纹理）、gl.TEXTURE_CUBE_MAP（立方体纹理）</strong>。WebGl中没法直接操作纹理对象，必须通过将纹理对象绑定到纹理单元上，然后通过操作纹理单元来操作纹理对象。</p>
            <pre><code>gl.bindTexture(target, texture) // 开启texture指定的纹理对象，并将其绑定到目标上。如果已经通过gl.activeTexture()激活了某个单元，则纹理对象也会绑定到这个纹理单元上。</code></pre>
            <p>配置纹理对象的参数，以此来设置纹理对象映射到图形上的具体方式，即告诉WebGl系统如何根据纹理坐标来获取颜色值，按何种方式重复填充纹理。</p>
            <pre><code>gl.texParameteri(target, pname, param)
// target：gl.TEXTURE_2D或者gl.TEXTURE_CUBE_MAP
// pname纹理参数：gl.TEXTURE_MAG_FILTER(放大方法，默认值gl.LINEAR)、gl.TEXTURE_MIN_FILTER(缩小方法，默认值gl.NEAREST_MIPMAP_LINEAR)、gl.TEXTURE_WRAP_S(水平填充方法，默认值gl.REPEAT)、gl.TEXTURE_WRAP_T(垂直填充方法，默认值gl.REPEAT)
// gl.NEAREST:使用原纹理上距离映射后像素中心最近的那个像素的颜色值
// gl.LINEAR：使用距离新像素中心最近的四个像素的颜色值的加权平均值
// gl.REPEAT：平铺式的重复纹理
// gl.MIRRPRED_REPEAT：镜像对称式的重复纹理
// gl.CLAMP_TO_EDGE:使用纹理图像边缘值</code></pre>
            <p>gl.texImage2D可以将纹理图像分配给纹理对象：</p>
            <pre><code>gl.texImage2D(target, level, internalformat, format, type, image)
// target：gl.TEXTURE_2D或者gl.TEXTURE_CUBE_MAP
// level：传入0
// intervalformat：图像的内部格式（gl.RGB、gl.RGBA、gl.ALPHA、gl.LUMINANCE、gl.LUMINANCE_ALPHA）
// format：纹理数据的格式，与intervalformat相同的值
// type：纹理数据的类型
// image：包含纹理图像的Image对象</code></pre>
            <p><strong>流明</strong>表示感知到的物体表面的亮度，通常使用物体表面的红、绿、蓝颜色分量的加权平均值来计算。</p>
            <h3 class="title">使用多幅纹理</h3>
            <canvas ref="canvasRef6" v-show="!img6" class="webgl-canvas spec-canvas"></canvas>
            <img :src="img6" v-show="img6" class="webgl-canvas spec-canvas"/>
            <p>片元着色器：</p>
            <pre><code>const FSHADER_SOURCE = `
    precision mediump float;
    uniform sampler2D u_Sampler0;
    uniform sampler2D u_Sampler1;
    varying vec2 v_TexCoord;
    void main() {
        vec4 color0 = texture2D(u_Sampler0, v_TexCoord);
        vec4 color1 = texture2D(u_Sampler1, v_TexCoord);
        gl_FragColor = color0 * color1;
    }
`</code></pre>
            <pre><code>const initTextures = (gl, n) => {
    const texture0 = gl.createTexture()
    const texture1 = gl.createTexture()
    const uSampler0 = gl.getUniformLocation(gl.program, 'u_Sampler0')
    const uSampler1 = gl.getUniformLocation(gl.program, 'u_Sampler1')
    const image0 = new Image()
    image0.onload = function () {
        loadTexture(gl, n, texture0, uSampler0, image0, 0)
    }
    image0.src = './logo.jpg'

    const image1 = new Image()
    image1.onload = function () {
        loadTexture(gl, n, texture1, uSampler1, image1, 1)
    }
    image1.src = './m.png'
    return true
}
let texUnit0 = false
let texUnit1 = false
const loadTexture = (gl, n, texture, uSampler, image, texUnit) => {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
    if (texUnit == 0) {
        gl.activeTexture(gl.TEXTURE0)
        texUnit0 = true
    } else {
        gl.activeTexture(gl.TEXTURE1)
        texUnit1 = true
    }
    gl.bindTexture(gl.TEXTURE_2D, texture)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image)
    gl.uniform1i(uSampler, texUnit)

    if (texUnit0 && texUnit1) {
        gl.clearColor(0.0, 0.0, 0.0, 1.0)
        gl.clear(gl.COLOR_BUFFER_BIT)
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, n)
    }
}</code></pre>
        </div>
    </MyArticle>
</template>

<script>
import { onMounted, ref } from 'vue'
import draw0 from './draw0'
import draw1 from './draw1'
import draw2 from './draw2'
import draw3 from './draw3'
import draw4 from './draw4'
import draw5 from './draw5'
export default {
    setup () {
        const canvasRef0 = ref(null)
        const img0 = ref(null)
        const canvasRef1 = ref(null)
        const img1 = ref(null)
        const canvasRef2 = ref(null)
        const img2 = ref(null)
        const canvasRef3 = ref(null)
        const img3 = ref(null)
        const canvasRef4 = ref(null)
        const img4 = ref(null)

        const canvasRef5 = ref(null)
        const img5 = ref(null)

        const canvasRef6 = ref(null)
        const img6 = ref(null)
        onMounted(() => {
            img0.value = draw0(canvasRef0.value, [-0.5, -0.5, 0.5, -0.5, 0, 0.5], [8.0, 16.0, 32.0], 3)
            img1.value = draw1(canvasRef1.value, [-0.5, -0.5, 8.0, 0.5, -0.5, 16.0, 0, 0.5, 32.0], 3)
            img2.value = draw2(canvasRef2.value, [-0.5, -0.5, 8.0, 1.0, 0.0, 0.0, 0.5, -0.5, 16.0, 0.0, 1.0, 0.0, 0, 0.5, 32.0, 0.0, 0.0, 1.0], 3)
            img3.value = draw2(canvasRef3.value, [-0.5, -0.5, 8.0, 1.0, 0.0, 0.0, 0.5, -0.5, 16.0, 0.0, 1.0, 0.0, 0, 0.5, 32.0, 0.0, 0.0, 1.0], 3, 'TRIANGLES')
            img4.value = draw3(canvasRef4.value, [-0.5, -0.5, 8.0, 0.5, -0.5, 16.0, 0, 0.5, 32.0], 3)
            // 纹理
            let arr = [-0.5, 0.5, 0.0, 1.0, -0.5, -0.5, 0.0, 0.0, 0.5, 0.5, 1.0, 1.0, 0.5, -0.5, 1.0, 0.0]
            const winWidth = document.documentElement.clientWidth || window.innerWidth
            if (winWidth > 1600) {
                arr = [-0.3, 0.8, 0.0, 1.0, -0.3, -0.8, 0.0, 0.0, 0.3, 0.8, 1.0, 1.0, 0.3, -0.8, 1.0, 0.0]
            }
            draw4(canvasRef5.value, arr, 4).then(res => {
                if (res) {
                    img5.value = res
                }
            })

            draw5(canvasRef6.value, arr, 4).then(res => {
                if (res) {
                    img6.value = res
                }
            })
        })
        return { canvasRef0, img0, canvasRef1, img1, canvasRef2, img2, canvasRef3, img3, canvasRef4, img4, canvasRef5, img5, canvasRef6, img6 }
    }
}
</script>

<style lang="scss">
    @use './style.scss';
</style>
